﻿module internal Octopus.OctopusLib.Impersonation

open System
open System.Runtime.InteropServices
open System.Runtime.ConstrainedExecution
open System.Runtime.InteropServices
open System.Security
open System.Security.Permissions
open System.Security.Principal
open Microsoft.Win32.SafeHandles

type private SafeTokenHandle () =
  inherit SafeHandleZeroOrMinusOneIsInvalid (true) with

  [<DllImport("kernel32.dll")>]
  [<ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)>]
  [<SuppressUnmanagedCodeSecurity>]
  //[<return: MarshalAs(UnmanagedType.Bool)>]
  static extern bool CloseHandle(IntPtr handle)

  override x.ReleaseHandle () = CloseHandle(x.handle)


module private PInvokeFunctions =
  [<DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)>]
  extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, SafeTokenHandle& phToken)


type private LogonType =
    /// <summary>
    /// This logon type is intended for users who will be interactively using the computer, such as a user being logged on  
    /// by a terminal server, remote shell, or similar process.
    /// This logon type has the additional expense of caching logon information for disconnected operations; 
    /// therefore, it is inappropriate for some client/server applications,
    /// such as a mail server.
    /// </summary>
    | LOGON32_LOGON_INTERACTIVE = 2
    /// <summary>
    /// This logon type is intended for high performance servers to authenticate plaintext passwords.
    /// The LogonUser function does not cache credentials for this logon type.
    /// </summary>
    | LOGON32_LOGON_NETWORK = 3
    /// <summary>
    /// This logon type is intended for batch servers, where processes may be executing on behalf of a user without 
    /// their direct intervention. This type is also for higher performance servers that process many plaintext
    /// authentication attempts at a time, such as mail or Web servers. 
    /// The LogonUser function does not cache credentials for this logon type.
    /// </summary>
    | LOGON32_LOGON_BATCH = 4
    /// <summary>
    /// Indicates a service-type logon. The account provided must have the service privilege enabled. 
    /// </summary>
    | LOGON32_LOGON_SERVICE = 5
    /// <summary>
    /// This logon type is for GINA DLLs that log on users who will be interactively using the computer. 
    /// This logon type can generate a unique audit record that shows when the workstation was unlocked. 
    /// </summary>
    | LOGON32_LOGON_UNLOCK = 7
    /// <summary>
    /// This logon type preserves the name and password in the authentication package, which allows the server to make 
    /// connections to other network servers while impersonating the client. A server can accept plaintext credentials 
    /// from a client, call LogonUser, verify that the user can access the system across the network, and still 
    /// communicate with other servers.
    /// NOTE: Windows NT:  This value is not supported. 
    /// </summary>
    | LOGON32_LOGON_NETWORK_CLEARTEXT = 8
    /// <summary>
    /// This logon type allows the caller to clone its current token and specify new credentials for outbound connections.
    /// The new logon session has the same local identifier but uses different credentials for other network connections. 
    /// NOTE: This logon type is supported only by the LOGON32_PROVIDER_WINNT50 logon provider.
    /// NOTE: Windows NT:  This value is not supported. 
    /// </summary>
    | LOGON32_LOGON_NEW_CREDENTIALS = 9

type private LogonProvider =
    /// <summary>
    /// Use the standard logon provider for the system. 
    /// The default security provider is negotiate, unless you pass NULL for the domain name and the user name 
    /// is not in UPN format. In this case, the default provider is NTLM. 
    /// NOTE: Windows 2000/NT:   The default security provider is NTLM.
    /// </summary>
    | LOGON32_PROVIDER_DEFAULT = 0
    | LOGON32_PROVIDER_WINNT35 = 1
    | LOGON32_PROVIDER_WINNT40 = 2
    | LOGON32_PROVIDER_WINNT50 = 3


[<PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")>]
let executeImpersonation domainName username password (f:unit->'a) =
    let mutable safeTokenHandle = new SafeTokenHandle()
    let returnValue = PInvokeFunctions.LogonUser(username, domainName, password, int LogonType.LOGON32_LOGON_NETWORK, int LogonProvider.LOGON32_PROVIDER_DEFAULT, &safeTokenHandle)
    if returnValue = false then
        let ret = Marshal.GetLastWin32Error()
        raise (new System.ComponentModel.Win32Exception(ret))
    else
        let mutable impersonatedUser : WindowsImpersonationContext = null
        try
          impersonatedUser <- WindowsIdentity.Impersonate(safeTokenHandle.DangerousGetHandle())
          f()
        finally
          impersonatedUser.Dispose()
          safeTokenHandle.Dispose()
